<template>
  <div class="gems-select"
    :class="{'is-disabled':disabled}"
    @mouseenter="states.mouseHover = true"
    @mouseleave="states.mouseHover = false"
  >
    <Tooltip
      placement="bottom-start"
      manual
      :popper-options="popperOptions"
      ref="tooltipRef"
      @click-outside="controlDropdown(false)"
      @click="toggleDropdown"
    >
      <Input
        v-model="states.inputValue"
        :disabled="disabled"
        :placeholder="filteredPlaceholder"
        ref="inputRef"
        :readonly="!filterable || !isDropdownShow"
        @input="debouncedFilter"
        @keydown.stop="handleKeyDown"
      >
        <template #suffix>
          <Icon v-if="showClearIcon" icon="circle-xmark" @mousedown.prevent="NOOP" @click.stop="onClear" class="gems-input__clear" />
          <Icon v-else icon="angle-down" class="header-icon" :class="{'is-active': isDropdownShow}"/>
        </template>
      </Input>
      <template #content>
        <div class="gems-select__loading" v-if="states.loading">
          <Icon icon="spinner" spinner/>
        </div>
        <div class="gems-select__nodata" v-else-if="filterable && filteredOptions.length === 0 && !states.loading">
          no matching data
        </div>
        <ul class="gems-select__menu">
          <template v-for="(item, index) in filteredOptions" :key="index">
            <li
              class="gems-select__menu-item"
              :class="{
                'is-disabled': item.disabled, 
                'is-selected': item.value === states.selectedOption?.value,
                'is-highlighted': index === states.hightlightIndex
                }"
              :id="`select-item-${item.value}`"
              @click.stop="handleSelect(item)"
            >
              <RenderVnode :vnode="renderLabel ? renderLabel(item) : item.label" />
            </li>
          </template>
        </ul>
      </template>
    </Tooltip>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, computed, watch, inject, type Ref } from 'vue'
import { isFunction, debounce } from 'lodash-es'
import type { SelectProps, SelectEmits, SelectOption, SelectState } from './types'
import Tooltip from '../Tooltip/Tooltip.vue';
import type { TooltipInstance } from '../Tooltip/types';
import Input from '../Input/Input.vue';
import type { InputInstance } from '../Input/types';
import Icon from '../Icon/Icon.vue';
import RenderVnode from '../Common/RenderVnode'
import { formItemContextKey } from '@/components/Form/types';

// Form表单项上下文，用于验证表单数据是否符合验证规则
const formItemContext = inject(formItemContextKey, null)
const runValidation = (trigger?: string) => {
  if (formItemContext) {
    formItemContext.validate(trigger).catch((e) => console.log(e.errors))
  }
}

defineOptions({
  name: 'GemsSelect',
})
const props = withDefaults(defineProps<SelectProps>(), {
  options: () => []
})
const emits = defineEmits<SelectEmits>()

// 当前显示的选项
const findOption = (value: string | number) => {
  return props.options.find(item => item.value === value) || null
}

const tooltipRef = ref() as Ref<TooltipInstance>
const inputRef = ref() as Ref<InputInstance>
const initialOption = findOption(props.modelValue)
const states = reactive<SelectState>({
  inputValue: initialOption ? initialOption.label : '',
  selectedOption: initialOption,
  mouseHover: false,
  loading: false,
  hightlightIndex: -1,
})
watch(() => props.modelValue, async (newValue) => {
  const updateOption = findOption(newValue)
  states.inputValue = updateOption ? updateOption.label : ''
  states.selectedOption = updateOption
  const updatedValue = updateOption ? updateOption.value : ''
  emits('change', updatedValue)
  emits('update:modelValue', updatedValue)
  runValidation('change')
})

// popper配置--使tooltip和select的宽度一致
const popperOptions: any = {
  modifiers: [
    {
      name: 'offset',
      options: {
        offset: [0, 9],
      },
    },
    {
      name: "sameWidth",
      enabled: true,
      fn: ({ state }: { state: any }) => {
          state.styles.popper.width = `${state.rects.reference.width}px`;
      },
      phase: "beforeWrite",
      requires: ["computeStyles"],
    }
  ],
}

// 支持过滤选项
const filteredOptions = ref<SelectOption[]>(props.options)
watch(() => props.options, (newVal: SelectOption[]) => {
  filteredOptions.value = newVal
})
const generateFilteredOptions = async (searchValue: string | number) => {
  if (!props.filterable) return
  if (props.filterMethod && isFunction(props.filterMethod)) {
    filteredOptions.value = props.filterMethod(searchValue)
    // 远程搜索
  }  else if (props.remote && props.remoteMethod && isFunction(props.remoteMethod)) {
    try {
      states.loading = true
      filteredOptions.value = await props.remoteMethod(searchValue)
    } catch(e) {
      console.error(e)
      filteredOptions.value = []
    } finally {
      states.loading = false
    }
  }
  else {
    filteredOptions.value = props.options.filter(item => item.label.includes(searchValue.toString()))
  }
  states.hightlightIndex = -1
}
// 修改placeholder为上一个过滤的选项值
const filteredPlaceholder = computed(() => {
  if (props.filterable && states.selectedOption && isDropdownShow.value) {
    return states.selectedOption.label
  } else {
    return props.placeholder
  }
})
// 输入框触发搜索事件--并实现防抖搜索
const onFilter = () => {
  generateFilteredOptions(states.inputValue)
}
const timeout = computed(() => {
  return props.remote ? 300 : 0
})
const debouncedFilter = debounce(onFilter, timeout.value)


// 显示清除按钮Icon
const showClearIcon = computed(() => {
  return props.clearable && states.mouseHover && states.inputValue != null && states.inputValue.toString().trim() !== '' && states.selectedOption
})
const onClear = () => {
  states.selectedOption = null
  states.inputValue = ''
  emits('clear')
  emits('change', '')
  runValidation('change')
  emits('update:modelValue', '')
}
const NOOP = () => {}

// 控制下拉菜单显示隐藏
const isDropdownShow = ref(false)
const controlDropdown = (show: boolean) => {
  if (show) {
    // filterable时，再次选择需清空inputValue
    if (props.filterable && states.selectedOption) {
      states.inputValue = ''
    }
    // 获取过滤后的选项
    if (props.filterable) {
      generateFilteredOptions(states.inputValue)
    }
    tooltipRef.value.show()
  } else {
    if (props.filterable) {
      // blur 的时候将之前选的值回灌到 input 中(首次选择后，再次点击准备选择，但未选择时，此时inputValue被清空了，需要重新拿回来，不然没值)
      states.inputValue = states.selectedOption ? states.selectedOption.label : ''
    }
    tooltipRef.value.hide()
    states.hightlightIndex = -1
  }
  isDropdownShow.value = show
  emits('visible-change', show)
}
const toggleDropdown = () => {
  if (props.disabled) return
  if (isDropdownShow.value) {
    controlDropdown(false)
  } else {
    controlDropdown(true)
  }
}

// 当前选中项
const handleSelect = (e: SelectOption) => {
  if (e.disabled) return
  states.inputValue = e.label
  states.selectedOption = e                 
  emits('change', e.value)
  runValidation('change')
  emits('update:modelValue', e.value)
  controlDropdown(false)
  inputRef.value.ref.focus()
}

// 处理键盘事件
const handleKeyDown = (e: KeyboardEvent) => {
  switch (e.key) {
    case 'Enter':
      if (!isDropdownShow.value) {
        controlDropdown(true)
      } else {
        if (states.hightlightIndex !== -1 && filteredOptions.value[states.hightlightIndex]) {
          handleSelect(filteredOptions.value[states.hightlightIndex])
        } else {
          controlDropdown(false)
        }
      }
      break
    case 'Escape':
      if (isDropdownShow.value) {
        controlDropdown(false)
      }
      break
    case 'ArrowUp':
      e.preventDefault()
      if (isDropdownShow.value && filteredOptions.value.length > 0) {
        if (states.hightlightIndex === -1 || states.hightlightIndex === 0) {
          states.hightlightIndex = filteredOptions.value.length - 1
        } else {
          states.hightlightIndex = states.hightlightIndex - 1
        }
      }
      break
    case 'ArrowDown':
    e.preventDefault()
      if (isDropdownShow.value && filteredOptions.value.length > 0) {
        if (states.hightlightIndex === -1 || states.hightlightIndex === filteredOptions.value.length - 1) {
          states.hightlightIndex = 0
        } else {
          states.hightlightIndex = states.hightlightIndex + 1
        }
      }
      break
    default:
      break
  }
}       
</script>